package net.stuffrepos.bricknetcenter.util.gui.inputcomponent;

import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Date;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
import net.stuffrepos.bricknetcenter.util.BuilderFactory;
import net.stuffrepos.bricknetcenter.util.BuilderFactory.Builder;
import net.stuffrepos.bricknetcenter.util.Inflector;
import net.stuffrepos.bricknetcenter.util.gui.ScrolledTextArea;
import net.stuffrepos.bricknetcenter.util.validation.Constraint;
import net.stuffrepos.bricknetcenter.util.validation.IntegerConstraint;
import net.stuffrepos.bricknetcenter.util.validation.Property;
import net.stuffrepos.bricknetcenter.util.validation.StringConstraint;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author Eduardo H. Bogoni <eduardobogoni@gmail.com>
 */
public class InputComponentFactory {

    private static final Log log = LogFactory.getLog(InputComponentFactory.class);

    public static enum InputType {

        Integer,
        ShortString,
        LongString,
        DateOnly,
        List
    }
    public static final int TEXT_FIELD_LENGTH_LIMIT = 255;
    private static final BuilderFactory.ClassKeyed<Property, InputType> inputTypes = new BuilderFactory.ClassKeyed<Property, InputType>(true) {

        @Override
        protected Class getSourceKey(Property source) {
            return source.getPropertyType();
        }
    };
    private static final BuilderFactory<InputSource, InputComponent, InputType> inputComponents =
            new BuilderFactory<InputSource, InputComponent, InputType>(false) {

                @Override
                protected InputType getSourceKey(InputSource source) {
                    return source.getInputType();
                }

                @Override
                protected InputType getParentKey(InputType key) {
                    return null;
                }
            };

    static {
        inputTypes.addBuilder(Integer.class, new Builder<Property, InputType>() {

            @Override
            public InputType build(Property source) {
                return InputType.Integer;
            }
        });
        inputTypes.addBuilder(String.class, new Builder<Property, InputType>() {

            @Override
            public InputType build(Property source) {
                if (((StringConstraint) source.getConstraint()).getMaxLength() <= TEXT_FIELD_LENGTH_LIMIT) {
                    return InputType.ShortString;
                } else {
                    return InputType.LongString;
                }
            }
        });
        inputTypes.addBuilder(Date.class, new Builder<Property, InputType>() {

            @Override
            public InputType build(Property source) {
                return InputType.DateOnly;
            }
        });
        inputTypes.addBuilder(Enum.class, new Builder<Property, InputType>() {

            @Override
            public InputType build(Property source) {
                return InputType.List;
            }
        });

        inputComponents.addBuilder(InputType.Integer, new Builder<InputSource, InputComponent>() {

            @Override
            public InputComponent build(InputSource source) {
                TextFieldInputComponent<Integer> inputComponent = new TextFieldInputComponent<Integer>(
                        new IntegerDocument(),
                        new TextFieldIntegerParser(),
                        source.getProperty().getConstraint());

                String minValue = Integer.toString(((IntegerConstraint) source.getProperty().getConstraint()).getMinValue());
                String maxValue = Integer.toString(((IntegerConstraint) source.getProperty().getConstraint()).getMaxValue());
                inputComponent.getInputComponent().setColumns(Math.max(minValue.length(), maxValue.length()));

                return inputComponent;
            }
        });
        inputComponents.addBuilder(InputType.ShortString, new Builder<InputSource, InputComponent>() {

            @Override
            public InputComponent build(InputSource source) {
                return new InputComponent(
                        new JTextField(Math.min(20, ((StringConstraint) source.getProperty().getConstraint()).getMaxLength())),
                        new TextComponentStringParser(),
                        source.getProperty().getConstraint());
            }
        });
        inputComponents.addBuilder(InputType.LongString, new Builder<InputSource, InputComponent>() {

            @Override
            public InputComponent build(InputSource source) {
                ScrolledTextArea textArea = new ScrolledTextArea();

                textArea.getTextArea().setColumns(20);
                textArea.getTextArea().setRows(5);
                textArea.getTextArea().setWrapStyleWord(true);

                return new InputComponent(
                        textArea,
                        new ScrolledTextAreaStringParser(),
                        source.getProperty().getConstraint());
            }
        });
        inputComponents.addBuilder(InputType.DateOnly, new Builder<InputSource, InputComponent>() {

            @Override
            public InputComponent build(InputSource source) {

                return new TextFieldInputComponent(
                        new PlainDocument(),
                        new TextFieldDateParser(),
                        source.getProperty().getConstraint());
            }
        });
        inputComponents.addBuilder(InputType.List, new Builder<InputSource, InputComponent>() {

            @Override
            public InputComponent build(final InputSource source) {
                JComboBox comboBox = new JComboBox();
                final InputComponent<JComboBox, Enum> inputComponent = new InputComponent<JComboBox, Enum>(
                        comboBox,
                        new ComboBoxEnumParser(),
                        source.getProperty().getConstraint()) {

                    @Override
                    protected void prepare(JComboBox component) {
                        component.setModel(new DefaultComboBoxModel(source.getProperty().getPropertyType().getEnumConstants()));
                    }
                };
                comboBox.addItemListener(new ItemListener() {

                    @Override
                    public void itemStateChanged(ItemEvent e) {                        
                        inputComponent.fireValueChanged();
                    }
                });


                return inputComponent;
            }
        });
    }

    public static InputComponent newInput(Property property, InputType inputType) {
        assert property != null;
        return inputComponents.getResult(new InputSource(property, inputType)).
                setLabelText(getInitialLabelText(property.getPropertyName()));
    }

    public static InputComponent newInput(Property property) {
        return newInput(property, (InputType) null);
    }

    public static InputComponent newInput(Class entityClass, String propertyName) {
        assert entityClass != null;
        assert propertyName != null;
        return newInput(Property.getProperty(entityClass, propertyName));
    }

    public static InputComponent newInput(Object entityInstance, String propertyName) {
        assert entityInstance != null;
        return newInput(entityInstance.getClass(), propertyName);
    }

    public static InputComponent newInput(Object entityInstance, String propertyName, InputType inputType) {
        assert entityInstance != null;
        assert propertyName != null;
        assert inputType != null;
        return newInput(Property.getProperty(entityInstance, propertyName), inputType);
    }

    private static String getInitialLabelText(String s) {
        return Inflector.humanize(s);
    }

    private static class InputSource {

        private Property property;
        private InputType inputType;

        public InputSource(Property property, InputType inputType) {
            this.property = property;
            this.inputType = inputType;
        }

        public InputSource(Property property) {
            this(property, null);
        }

        public Property getProperty() {
            return property;
        }

        public InputType getInputType() {
            if (inputType == null) {
                return inputTypes.getResult(property);
            } else {
                return inputType;
            }
        }
    }

    private static class TextFieldInputComponent<ValueType> extends InputComponent<JTextField, ValueType> {

        public TextFieldInputComponent(Document document, InputComponentValueParser<JTextField, ValueType> valueParser, Constraint<ValueType> constraint) {
            super(new JTextField(), valueParser, constraint);
            getInputComponent().setOpaque(true);
            getInputComponent().setDocument(document);
            getInputComponent().getDocument().addDocumentListener(new DocumentListener() {

                @Override
                public void insertUpdate(DocumentEvent e) {
                    fireValueChanged();
                }

                @Override
                public void removeUpdate(DocumentEvent e) {
                    fireValueChanged();
                }

                @Override
                public void changedUpdate(DocumentEvent e) {
                    fireValueChanged();
                }
            });
        }
    }
}
